import 'dart:convert';

import 'package:im_app/im/util/ImCacheFactory.dart';
import 'package:im_app/im/util/MapUtil.dart';
import 'package:logger/logger.dart';
import 'package:sqflite/sqflite.dart';

import '../pojo/ImPojo.dart';
import '../util/DateUtil.dart';
import 'BaseDao.dart';
import 'ImGroupUserDao.dart';
import 'ImUserDao.dart';

class ImGroupDao extends BaseDao {

  final _logger = Logger(printer: PrettyPrinter(),);

  Future<ImGroup> getById(int id) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_group where id = ? limit 1", [id]);
    if (list != null && list.isNotEmpty) {
      return Future.value(_toImGroup(list[0]));
    }
    return Future.value(null);
  }

  Future<ImGroup?> getByUserId(String currentUserId, String fromUserId, String toUserId) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_group where currentUserId = ? and (fromUserId = ? and "
        "toUsreId = ?) or (fromUserId = ? and toUserId = ?) limit 1",
        [currentUserId, fromUserId, toUserId, toUserId, fromUserId]);
    if (list != null && list.length > 0) {
      return Future.value(_toImGroup(list[0]));
    }
    return null;
  }

  Future<ImGroup?> getByCodeAndUserId(String groupCode, String currentUserId) async {
    Database? database = getDatabase(); //await getReadOnlyDatabase(); //ImSingleton.getInstance()?.dataBase;
    var imGroupUserDao = ImGroupUserDao();
    List? list = await database?.rawQuery("select * from im_group where code = ? and currentUserId = ? limit 1",
        [groupCode, currentUserId]);
    if (list != null && list.length > 0) {
      var imGroup = _toImGroup(list[0]);

      var imGroupUsers = await imGroupUserDao.getByGroupId(imGroup.id!);
      imGroup.groupUserList = imGroupUsers.toSet();
      imGroup.currentGroupUser = imGroup.groupUserList?.firstWhere((gu)=>gu.userId == currentUserId);
      return Future.value(imGroup);
    }
    return Future.value(null);
  }

  Future<void> loadGroupUsers(ImGroup imGroup) async {
    var groupUserDao = ImGroupUserDao();
    var userDao = ImUserDao();
    var groupUsers = await groupUserDao.getByGroupId(imGroup!.id!);
    for (var groupUser in groupUsers) {
      var user = await userDao.getByUserId(groupUser.userId!);
      groupUser.user = user!;
    }
    imGroup!.groupUserList = groupUsers.toSet();
    imGroup!.currentGroupUser = groupUsers.where((gu)=>gu.userId == imGroup.currentUserId).first;
  }

  Future<List<ImGroup>> getByUserAndSize(String userId, int size) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var imGroupUserDao = ImGroupUserDao();
    List<Map<String, dynamic>>? list = await database?.rawQuery("select * from im_group where currentUserId = ? limit 0, $size", [userId]);
    if (list != null && list.isNotEmpty) {
      List<ImGroup> result = list.map((map){return _toImGroup(map);}).toList();
      for (var group in result) {
        var imGroupUsers = await imGroupUserDao.getByGroupId(group.id!);
        group.groupUserList = imGroupUsers.toSet();
        for (var gu in imGroupUsers) {
          gu.user = await ImCacheFactory.getInstance().getImUser(gu.userId);
        }
      }
      return Future.value(result);
    }
    return Future.value([]);
  }

  Future<List<ImGroup>> getByCodesAndUserId(List<String> codeList, String userId) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    String param = codeList.map((p)=> '?').join(",");
    String sql = "select * from im_group where code in ($param) and currentUserId = ?";
    List<dynamic> params = [];
    params.addAll(codeList);
    params.add(userId);
    List<Map<String, dynamic?>>? list = await database?.rawQuery(sql, params);
    return list != null ? Future.value(list.map((map){return _toImGroup(map);}).toList()) : Future.value(null);
  }

  Future<List<ImGroup>> getNeedSendGroupsByUserId(String userId, int pageSize) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    String sql = "select * from im_group g where g.currentUserId = ? and g.status = '-1' limit 0, $pageSize";
    List<Map<String, dynamic?>>? list = await database?.rawQuery(sql, [userId]);
    List<ImGroup>? groups = list?.map((map)=>_toImGroup(map)).toList();
    if (groups != null) {
      var groupUserDao = ImGroupUserDao();
      for (var group in groups) {
        var groupUsers = await groupUserDao.getByGroupId(group.id!);
        group.groupUserList = groupUsers?.toSet();
      }
    }
    return groups ?? [];
  }

  Future<void> batchInserts(List<ImGroup> imGroupList) async {
    if (imGroupList.isEmpty) return;
    Database? database = getDatabase(); //ImSingleton.getInstance().dataBase;
    var imGroupUserDao = ImGroupUserDao();
    for (var imGroup in imGroupList) {
      var data = _toInsertValueMap(imGroup);
      int? id = await database?.insert("im_group", data);
      var imGroupUsers = imGroup.groupUserList;
      if (id != null && imGroupUsers != null) {
        await imGroupUserDao.batchInserts(id, imGroupUsers);
      }
    }
  }

  Future<void> batchUpdates(List<ImGroup> imGroupList) async {
    if (imGroupList.isEmpty) return;
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    Batch? batch = database?.batch();
    for (var imGroup in imGroupList) {
      var data = _toUpdateValueMap(imGroup);
      batch?.update("im_group", data, where: "id=?", whereArgs:[imGroup.id]);
    }
    List<Object?>? list = await batch?.commit();
    _logger.i("batch json: " + jsonEncode(list));
  }

  Future<int?> getGroupByUserIds(List<String> userIds) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var param = userIds.map((userId)=>"?").join(",");
    var sql = 'SELECT groupId groupId, COUNT(*) t FROM im_group_user WHERE USERiD IN ($param) GROUP BY groupId HAVING t = ?';
    List<Object> whereParam = List.of(userIds);
    whereParam.add(userIds.length);
    List<Map<String, dynamic?>>? list = await database?.rawQuery(sql, whereParam);
    List<int>? groupIds = list?.map((map)=>MapUtil.getInt(map, "groupId")).toList();
    var length = groupIds?.length??0;
    if (length >= 1) {
      var param1 = groupIds!.map((gid)=>"?").join(",");
      var sql1 = 'SELECT groupId, COUNT(*) t FROM im_group_user WHERE groupId IN ($param1) GROUP BY groupId HAVING t = ?';
      List<Object> whereParam1 = List.of(groupIds);
      whereParam1.add(groupIds.length);

      List<Map<String, dynamic?>>? list = await database?.rawQuery(sql1, whereParam1);
      List<int>? groupIds1 = list?.map((map)=>MapUtil.getInt(map, "groupId")).toList();
      var length1 = groupIds1?.length??0;
      if (length1 > 0) {
        return groupIds1![0];
      }
    }

    return null;
  }

  Map<String, dynamic?> _toUpdateValueMap(ImGroup imGroup) {
    Map<String, dynamic?> map = {};
    map["status"]= imGroup.status?.value;
    map['name'] = imGroup.name;
    map['ownerId'] = imGroup.ownerId;
    map['description'] = imGroup.description;
    map['version'] = imGroup.version??0 + 1;
    map['ext1'] = imGroup.ext1;
    map['ext2'] = imGroup.ext2;
    map['firstLetter'] = imGroup.firstLetter;
    map['updateTime'] = DateUtil.formatDate(DateTime.now());
    return map;
  }

  Map<String, dynamic?> _toInsertValueMap(ImGroup imGroup) {
    Map<String, dynamic?> map = {};
    map['code'] = imGroup.code;
    map['currentUserId'] = imGroup.currentUserId;
    map["status"]= imGroup.status?.value;
    map['name'] = imGroup.name;
    map['ownerId'] = imGroup.ownerId;
    map['description'] = imGroup.description;
    map['version'] = 1;
    map['groupType'] = ImGroupType.chat.name;
    map['ext1'] = imGroup.ext1;
    map['ext2'] = imGroup.ext2;
    map['firstLetter'] = imGroup.firstLetter;
    map['createTime'] = DateUtil.formatDate(DateTime.now());
    map['updateTime'] = DateUtil.formatDate(DateTime.now());
    return map;
  }

  ImGroup _toImGroup(Map<String, dynamic> map) {
    var imGroup = ImGroup(code: MapUtil.getString(map, "code"));
    imGroup.id = MapUtil.getInt(map, "id");
    imGroup.name = MapUtil.getStringOrNull(map, "name");
    imGroup.userName = MapUtil.getStringOrNull(map, "userName");
    imGroup.currentUserId = MapUtil.getStringOrNull(map, "currentUserId");
    imGroup.code = MapUtil.getString(map, "code");
    imGroup.status = ImGroupStatus.ofValue(MapUtil.getInt(map, "status"));
    imGroup.groupType = ImGroupType.ofName(MapUtil.getString(map, "groupType"));
    imGroup.description = MapUtil.getStringOrNull(map, "description");
    imGroup.version = MapUtil.getInt(map, "version");
    imGroup.ownerId = MapUtil.getStringOrNull(map, "ownerId");
    imGroup.ext1 = MapUtil.getStringOrNull(map, "ext1");
    imGroup.ext2 = MapUtil.getStringOrNull(map, "ext2");
    imGroup.firstLetter = MapUtil.getStringOrNull(map, "firstLetter");
    imGroup.createTime = MapUtil.getDate(map, "createTime");
    imGroup.updateTime = MapUtil.getDate(map, "updateTime");
    return imGroup;
  }
}